﻿using System;
using System.Collections.Generic;
using Microsoft.ResourceManagement.Client.WsEnumeration;
using Microsoft.ResourceManagement.ObjectModel;

namespace Microsoft.ResourceManagement.Client
{
    public class EnumerationResultEnumerator : IEnumerator<RmResource>, IEnumerable<RmResource>
    {
        WsEnumerationClient client;
        List<RmResource> results;
        int resultIndex;
        bool endOfSequence;
        EnumerationContext context;
        String filter;
        String[] attributes;
        Dictionary<string, bool> sortAttributes;
        RmResource current;
        RmResourceFactory resourceFactory;
        bool disposed = false;
        int currentTotal = 0;
        int? maxRows;
        
        internal EnumerationResultEnumerator(WsEnumerationClient client, RmResourceFactory factory, 
            String filter, String[] attributes) 
            : this(client, factory, filter, attributes, null, null)
        {
        }

        internal EnumerationResultEnumerator(WsEnumerationClient client, RmResourceFactory factory,
            String filter, String[] attributes, int? maxRows)
            : this(client, factory, filter, attributes, maxRows, null)
        {
        }

        internal EnumerationResultEnumerator(WsEnumerationClient client, RmResourceFactory factory, 
            String filter, String[] attributes, int? maxRows, Dictionary<string, bool> sortAttributes)
        {
            results = new List<RmResource>();
            this.client = client;
            this.filter = filter;
            this.resourceFactory = factory;
            this.attributes = attributes;
            this.maxRows = maxRows;
            this.sortAttributes = sortAttributes;
        }

        #region IEnumerator<RmResource> Members

        public RmResource Current
        {
            get { return current; }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if (!disposed)
            {
                this.context = null;
                this.results.Clear();
                this.results = null;
                this.disposed = true;
            }
        }

        #endregion

        #region IEnumerator Members

        object System.Collections.IEnumerator.Current
        {
            get { return current; }
        }

        public bool MoveNext()
        {
            if (disposed)
            {
                throw new InvalidOperationException("The query results cannot be enumerated more than once.");
            }

            if (this.maxRows != null && this.currentTotal >= this.maxRows)
            {
                this.current = null;
                return false;
            }
            
            lock (this.client)
            {
                if (resultIndex < results.Count)
                {
                    this.current = results[resultIndex++];
                    this.currentTotal++;
                    return true;
                }
                else
                {
                    PullResponse response;
                    if (this.context == null)
                    {
                        if (resultIndex > 0)
                        {
                            // case: previous pull returned an invalid context
                            return false;
                        }
                        EnumerationRequest request = new EnumerationRequest(filter);
                        if (this.maxRows != null)
                        {
                            if ((this.maxRows.Value - this.currentTotal) < request.MaxElements)
                            {
                                request.MaxElements = this.maxRows.Value - this.currentTotal;
                            }
                        }
                        if (attributes != null)
                        {
                            request.Selection = new List<string>();
                            request.Selection.AddRange(this.attributes);

                        }
                        if (sortAttributes != null)
                        {
                            Sorting sorting = new Sorting();
                            List<SortingAttribute> sortList = new List<SortingAttribute>();
                            foreach (KeyValuePair<string, bool> item in sortAttributes)
                            {
                                SortingAttribute sortAttribute = new SortingAttribute()
                                {
                                    Value = item.Key,
                                    Ascending = item.Value
                                };
                                sortList.Add(sortAttribute);
                            }
                            sorting.SortingAttribute = sortList;
                            request.Sorting = sorting;
                        }
                        response = client.Enumerate(request);
                        this.endOfSequence = response.EndOfSequence != null;
                    }
                    else
                    {
                        if (this.endOfSequence == true)
                        {
                            // case: previous pull returned an end of sequence flag
                            this.current = null;
                            return false;
                        }
                        PullRequest request = new PullRequest();
                        request.EnumerationContext = this.context;
                        if (this.maxRows != null)
                        {
                            if ((this.maxRows.Value - this.currentTotal) < request.MaxElements)
                            {
                                request.MaxElements = this.maxRows.Value - this.currentTotal;
                            }
                        }
                        response = client.Pull(request);
                    }

                    if (response == null)
                        return false;
                    resultIndex = 0;
                    this.results = resourceFactory.CreateResource(response);
                    this.context = response.EnumerationContext;
                    this.endOfSequence = response.IsEndOfSequence;
                    if (this.results.Count > 0)
                    {
                        this.current = results[resultIndex++];
                        this.currentTotal++;
                        return true;
                    }
                    else
                    {
                        this.current = null;
                        return false;
                    }
                }
            }
        }

        public void Reset()
        {
            if (!disposed)
            {
                this.results.Clear();
                this.context = null;
            }
        }

        #endregion

        #region IEnumerable<RmResource> Members

        public IEnumerator<RmResource> GetEnumerator()
        {
            return this;
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this;
        }

        #endregion
    }
}
